package de.jmda.sample.junit;

import static de.jmda.core.util.StringUtil.sb;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

import de.jmda.core.mproc.ProcessingUtilities;
import de.jmda.gen.GeneratorException;
import de.jmda.gen.GeneratorValidation;
import de.jmda.gen.LineIndenter;
import de.jmda.gen.java.ClassGenerator;
import de.jmda.gen.java.InstanceMethodGenerator;
import de.jmda.gen.java.ModifiersUtil.VisibilityModifier;
import de.jmda.gen.java.impl.DefaultClassGenerator;
import de.jmda.gen.java.impl.DefaultInstanceMethodGenerator;
import de.jmda.gen.java.impl.ManagedImportStatementsGenerator;
import de.jmda.gen.java.naming.ImportManager;
import de.jmda.gen.java.naming.ImportManagerProvider;

public class TestSuiteBuilder
{
	private final static Logger LOGGER = Logger.getLogger(TestSuiteBuilder.class);

	public final static String CLASSNAME = "JUTPackageTestSuite";

	private Map<PackageElement, Set<TypeElement>> testSuiteData;
	
	public TestSuiteBuilder(Map<PackageElement, Set<TypeElement>> testSuiteData)
  {
		// parameter check
		// TODO check integrity of map (all type elements of a set belong to the
		//      same package which corresponds to the key, ...)
		// parameter check end

		this.testSuiteData = testSuiteData;
  }

	public Map<PackageElement, StringBuffer> buildPackageTestSuites()
			throws GeneratorException, IOException
  {
		Map<PackageElement, StringBuffer> result =
				new HashMap<PackageElement, StringBuffer>();

		for (PackageElement packageElement : testSuiteData.keySet())
		{
			Set<TypeElement> typeElements = testSuiteData.get(packageElement);

			File packageDirectory = getPackageDirectory(typeElements);

			if (packageDirectory != null)
			{
				result.put(packageElement, buildPackageSuite(typeElements));
			}
		}
		
	  return result;
  }

	private StringBuffer buildPackageSuite(Set<TypeElement> typeElements)
			throws GeneratorException
  {
		ClassGenerator generator = new DefaultClassGenerator();
		
		String packagename =
				ProcessingUtilities.getPackageQualifiedName(
						typeElements.iterator().next());

		ImportManager importManagerBackup =
				ImportManagerProvider.newImportManager(packagename, CLASSNAME);
		ImportManager importManager =
				ImportManagerProvider.getImportManager();
		
		generator.setPackageName(packagename);
		generator.setImportStatementsGenerator(
				new ManagedImportStatementsGenerator(importManager));
		generator.setVisibility(VisibilityModifier.PUBLIC);
		generator.setClassName(CLASSNAME);
		generator.setExtendsClause(importManager.useType(Runner.class));
		generator.addAnnotation(
				RunWith.class, sb(importManager.useType(Suite.class) + ".class"));
		generator.addAnnotation(
				SuiteClasses.class, buildAnnotationParameters(typeElements));
		generator.addMethodGenerator(
				buildMethodDeclarationGeneratorGetDescription());
		generator.addMethodGenerator(buildMethodDeclarationGeneratorRun());

		GeneratorValidation.validate(generator);
		
		StringBuffer result = generator.generate();

		LOGGER.debug("generated\n" + result);
		
		ImportManagerProvider.restoreImportManager(importManagerBackup);

		return result;
  }

	public StringBuffer buildSuiteForPackageTestSuites(
			Set<PackageElement> packageElements) throws GeneratorException
  {
		StringBuffer result;

		ClassGenerator generator = new DefaultClassGenerator();

		generator.setVisibility(VisibilityModifier.PUBLIC);
		generator.setClassName(CLASSNAME + "s");
		generator.setExtendsClause(Runner.class.getName());
		generator.addAnnotation(RunWith.class, sb(Suite.class.getName() + ".class"));
		generator.addAnnotation(
				SuiteClasses.class,
				buildAnnotationParametersForPackageTestSuites(packageElements));
		generator.addMethodGenerator(
				buildMethodDeclarationGeneratorGetDescription());
		generator.addMethodGenerator(buildMethodDeclarationGeneratorRun());

		GeneratorValidation.validate(generator);
		
		result = generator.generate();

		LOGGER.debug("generated\n" + result);

		return result;
  }

	private StringBuffer buildAnnotationParameters(Set<TypeElement> typeElements)
  {
		List<String> classes = new ArrayList<String>();
		
		ImportManager importManager = ImportManagerProvider.getImportManager();

		for (TypeElement typeElement : typeElements)
		{
			classes.add(
					importManager.useType(typeElement.getQualifiedName().toString()) +
					".class");
		}

		return sb("{" + StringUtils.join(classes.toArray(), ", ") + "}");
  }

	private StringBuffer buildAnnotationParametersForPackageTestSuites(
      Set<PackageElement> packageElements)
  {
		List<String> classes = new ArrayList<String>();
		
		for (PackageElement packageElement : packageElements)
		{
			classes.add(
					packageElement.getQualifiedName().toString() +
					"." + CLASSNAME + ".class");
		}

		return sb("{" + StringUtils.join(classes.toArray(), ", ") + "}");
  }

	private InstanceMethodGenerator buildMethodDeclarationGeneratorGetDescription()
  {
		InstanceMethodGenerator result =
				new DefaultInstanceMethodGenerator();
		
		result.setLineIndenter(new LineIndenter("\t", 1));
		result.addAnnotation(Override.class);
		result.setVisibility(VisibilityModifier.PUBLIC);
		result.setType(Description.class);
		result.setName("getDescription");
		result.setMethodBody(
				"return " + Description.class.getName() +
				".createSuiteDescription(" +
						"\"test suite for package \" + getClass().getPackage().getName());");
		
	  return result;
  }

	private InstanceMethodGenerator buildMethodDeclarationGeneratorRun()
  {
		InstanceMethodGenerator result =
				new DefaultInstanceMethodGenerator();
		
		result.setLineIndenter(new LineIndenter("\t", 1));
		result.addAnnotation(Override.class);
		result.setVisibility(VisibilityModifier.PUBLIC);
		result.setName("run");
		result.addParameter(RunNotifier.class, "notifier");
		result.setMethodBody("// intentionally left blank for now");

		return result;
  }

	private File getPackageDirectory(Set<TypeElement> typeElements)
  {
		if (typeElements.size() == 0)
		{
		  return null;
		}
		
		return
				new File(
						ProcessingUtilities.getPackageQualifiedName(
								typeElements.iterator().next()).replaceAll("\\.","/"));
  }
}